/*
 * Copyright (c) 2008, Xiong Yingfei, the University of Tokyo All rights
 * reserved.
 */
package ac.jp.u_tokyo.SyncLib.dictionaries;

import java.util.Collection;
import java.util.LinkedList;

import ac.jp.u_tokyo.SyncLib.CompilerParameters;
import ac.jp.u_tokyo.SyncLib.Mod;
import ac.jp.u_tokyo.SyncLib.SyncFactory;
import ac.jp.u_tokyo.SyncLib.SynchronizationFailedException;
import ac.jp.u_tokyo.SyncLib.util.Pair;
import ac.jp.u_tokyo.SyncLib.util.TwoIterable;

public class MatchingDictMapCombinator extends DictMapCombinatorBase
{
	protected KeyFactory<Object>[] _keyFactories;

	public MatchingDictMapCombinator(SyncFactory innerSync,
			KeyFactory[] keyFactories, int nonDictCount)
	{
		super(innerSync, keyFactories.length, nonDictCount);
		_keyFactories = keyFactories;
	}

	private class MatchingSynchronizationPerformer extends
			SynchronizationPerformer
	{

		public MatchingSynchronizationPerformer(Mod[] modifications,
				InfoProvider provider)
		{
			super(modifications, provider);
		}

		@Override
		protected void match() throws SynchronizationFailedException
		{
			Pair<Integer, Object> unmatched;
			while ((unmatched = getFirstUnmatched(unmatchedKeys)) != null)
			{
				Object[] keys = new Object[getDictCount()];
				if (!internalMatch(0, unmatched.getValue1(), unmatched
						.getValue2(), keys))
				{
					throw new SynchronizationFailedException();
				}
			}
		}

		private boolean internalMatch(int i, int matchingIndex,
				Object matchingKey, Object[] selectedKeys)
		{
			if (i >= getDictCount())
			{
				try
				{
					syncMatched(selectedKeys);
					return true;
				}
				catch (SynchronizationFailedException e)
				{
					return false;
				}
			}
			else if (i == matchingIndex)
			{
				selectedKeys[i] = matchingKey;
				isNewKey[i] = false;
				return internalMatch(i + 1, matchingIndex, matchingKey,
						selectedKeys);
			}
			else
			{
				for (Object key : unmatchedKeys[i])
				{
					selectedKeys[i] = key;
					isNewKey[i] = false;
					if (internalMatch(i + 1, matchingIndex, matchingKey,
							selectedKeys)) return true;
				}
				selectedKeys[i] = createNewKey(i, keysCreated);
				isNewKey[i] = true;
				return internalMatch(i + 1, matchingIndex, matchingKey,
						selectedKeys);
			}
		}

		private Collection[] keysCreated = new Collection[getDictCount()];
		{
			for (int i = 0; i < keysCreated.length; i++)
			{
				keysCreated[i] = new LinkedList();
			}
		}

		private boolean[] isNewKey = new boolean[getDictCount()];

		@Override
		protected void createNewKeysIfNecessary(Object[] selectedKeys)
		{
			if (!CompilerParameters.UseGlobalKeyFactory)
				for (int i = 0; i < getDictCount(); i++)
				{
					//				if (selectedKeys[i] == null) selectedKeys[i] = createKey(i);
					if (isNewKey[i])
					{
						keysCreated[i].add(selectedKeys[i]);
					}
				}
		}

		//		private Object createKey(int i)
		//		{
		////			if (keysCreated[i] == null) keysCreated[i] = new LinkedList();
		//			Object result = createNewKey(i, keysCreated);
		//			keysCreated[i].add(result);
		//			return result;
		//		}

		private Object createNewKey(int i, Collection[] keysCreated)
		{
			TwoIterable oldKeySet ;
			if (CompilerParameters.UseGlobalKeyFactory)
			{
				oldKeySet = null;
			}
			else
			{
				oldKeySet = new TwoIterable(
						dictmods[i].keySet(), new TwoIterable(keysCreated[i],
								_provider.getDictValue(i).keySet()));
			}
			return _keyFactories[i].createNewKey(oldKeySet);
		}

	}

	@Override
	protected Mod[] doResynchronize(Object[] values, Mod[] modifications)
			throws SynchronizationFailedException
	{
		Mod[] result = new MatchingSynchronizationPerformer(modifications,
				new ResyncInfoProvider(values)).doSynchronization();
		return result;
	}

	@Override
	protected Mod[] doSynchronize(Mod[] modifications)
			throws SynchronizationFailedException
	{
		try
		{
			Mod[] result = new MatchingSynchronizationPerformer(modifications,
					new SyncInfoProvider()).doSynchronization();
			return result;
		}
		catch (SynchronizationFailedException e)
		{
			throw e;
		}
	}

	@Override
	public String toString()
	{
		return "MatchingDictMapCombinator@" + hashCode();
	}

}
